package cn.tyz.kotlin.ui

import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import cn.tyz.kotlin.R
import cn.tyz.kotlin.model.PlayerCmd
import cn.tyz.kotlin.utils.LogUtil
import okhttp3.OkHttpClient
import okhttp3.Request

/**
 * @author : tyz
 * @ClassName: ObjectActivity
 * @Description:
 * @time 2018/5/19 21:17
 * 类的构成由构造函数和初始化代码块、属性（字段）、函数（方法）、内部类（嵌套类）、对象声明五部分组成
 * 在构造函数中声明的参数，它们默认属于类的公有字段，可以直接使用，如果你不希望别的类访问到这个变量，可以用private修饰。
 *
 * Kotlin 默认会为每个变量和方法添加 final 修饰符
 * 在 Kotlin 中默认每个类都是不可被继承的。如果你确定这个类是会被继承的，那么你需要给这个类添加 open 修饰符
 */
class  ObjectActivity: BaseActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.act_common)
        var quantity = 5
        val price = 20.3
        val name = "大米"
//        var表示变量（可读写），val表示常量（只读）。
        println("单价:$price")
        println("数量:$quantity")
        println("产品:$name 总计:${quantity * price}")

        name?.let {
            LogUtil.i("ObjectActivity","非空输出 "+name)
            // test?:let
        }
        Person("习近平").printName()  //Kotlin 创建对象的时候并不需要new关键字
        Customer("李克强")
        Customer2("李克强")
        var test1 = Test(1)  // 这里和Java不同的点是，没有new这个关键字
        var test2 = Test(1,1)

        Test2(1)                 // num1 = 1	 num2 = 20
        Test2(1,1)        // num1 = 1	 num2 = 1

        Cat(Dog()).bark()

        val delegate = Delegate()
        val cat = Cat2(delegate, delegate)
        cat.bark()
        cat.eat()

        toast("扩展activity的toast方法")
        var d = D()
        d.printFoo()
        d.fun0()

        showDataClass()
        showsSealedClass()
        showEnum()
        showNestedClass()
        showObject()
        showFoo()
        try {
            getHtml()
        } catch (e: Exception) {
        }
    }

    /** 如果主构造函数没有注解或可见性说明，则 constructor 关键字是可以省略
     * 在构造函数中声明的参数，它们默认属于类的公有字段，可以直接使用，如果你不希望别的类访问到这个变量，可以用private修饰。*/
    class Person(val name: String) {
        fun printName() {
            Log.i("ObjectActivity"," Person.name = ${name}")
        }
    }

    /**如果构造函数有注解或可见性声明，则 constructor 关键字是不可少的，并且可见性（public）应该在前*/
//    class Person public @inject constructor (name: String) {...}


   /** 主构造函数不能包含任意代码。初始化代码可以放在以 init 做前缀的初始化块内*/
    class Customer(name: String) {
        init {
            Log.i("ObjectActivity","Customer initialized with value ${name}")
        }
    }

  /** 注意主构造函数的参数可以用在初始化块内，也可以用在类的属性初始化申明处：
   1、函数的申明可以是val也可以是var
   2、当不在主构造函数中声明又想当全局变量使用，可在类中声明，主函数中申明是简化了其写法。

   */
    class Customer2(name: String) {
         // 属性初始化申明
          val customerKry = name.toUpperCase()
         // 初始化块
          init {
              val customerKry = name.toUpperCase()
          }
    }


    /** 次构造函数
    一个类当然会有多个构造函数的可能，只有主构造函数可以写在类头中，其他的次级构造函数(Secondary Constructors)就需要写在类体中了。
    由于次级构造函数不能直接将参数转换为字段，所以需要手动声明一个 age 字段，并为 age 字段赋值。

    1、次构造函数不能有声明 val 或 var
    2、如果类有一个主构造函数（无论有无参数），每个次构造函数需要直接或间接委托给主构造函数，用this关键字
    3、当没有主构造参数时，创建次构造函数; 没有主构造函数，使用委托this()会报错误*/
    class Person3 {
        constructor() { }

        constructor(name: String):this() { }

        constructor(name: String, age: Int) : this(name) {
            Log.i("ObjectActivity","次构造函数数 name = $name  num = $age")
        }
    }

    /**二级构造函数
    类也可以有二级构造函数，需要加前缀 constructor:*/
    class Test constructor(num: Int){ // 这里是为了代码清晰，故而没有隐藏constructor关键字
        init {
            Log.i("ObjectActivity","二级构造函数  num = $num")
        }
        //二级构造函数中的参数1(num)，是委托了主构造函数的参数num
        constructor(num : Int, num2: Int) : this(num) {
            var sum = num + num2
            Log.i("ObjectActivity","二级构造函数  num = $num, num2=$num2  "+sum) // 输出num = 1, num2=2 3
        }
 /*       可以看出，当实例化类的时候只传1个参数的时候，只会执行init代码块中的代码。
        当传2个参数的时候，除了执行了init代码块中代码外，还执行了二级构造函数中的代码。*/
    }

    /**2.3、当类的主构造函数都存在默认值时的情况
    在JVM上，如果类主构造函数的所有参数都具有默认值，编译器将生成一个额外的无参数构造函数，它将使用默认值。
    这使得更容易使用Kotlin与诸如Jackson或JPA的库，通过无参数构造函数创建类实例。
    同理可看出，当类存在主构造函数并且有默认值时，二级构造函数也适用*/
    class Test2 constructor(num1: Int = 10 , num2: Int = 20){

        init {
            println("num1 = $num1\t num2 = $num2")
        }

        constructor(num1 : Int = 1, num2 : Int = 2, num3 : Int = 3) : this(num1 , num2){
            println("num1 = $num1\t num2 = $num2 \t num3 = $num3")
        }
    }

    /** 4.5.1 扩展方法
    你可以给父类添加一个方法，这个方法将可以在所有子类中使用
    例如，在 Android 开发中，我们常常使用这样的扩展函数：
    这样，我们就可以在每一个Activity中直接使用toast()函数了。
    需要注意的是扩展方法是静态解析的，而并不是真正给类添加了这个方法。  */

    fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(this, message, duration).show()
    }


   /* 4.6.1 强转与智能转换
    if (animal is Dog) {
        //在这里 animal 被当做 Dog 的对象来处理
        animal.bark()
    }
    如果智能转换的对象是一个全局变量，这个变量可能在别的地方被改变赋值，所以你必须手动判断与转换它的类型。
    if (animal is Dog) {
        //在这里你必须手动强转为Dog的对象
        (animal as Dog).bark()
    }*/

   /** 4.7 伴生对象
   由于 Kotlin 没有静态方法。在大多数情况下，官方建议是简单地使用 包级 函数。如果你需要写一个可以无需用一个类的
   实例来调用、但需要访问类内部的函数(例如,工厂方法或单利),你可以把它写成一个用 companion修饰的对象内的方法。
   我们称companion修饰的对象为伴生对象。
    * */
    class StringUtils {
        companion object {
            fun isEmpty(str: String): Boolean {
                return "" == str
            }
        }
    }

//    伴生对象实现接口
    interface Factory<T>{
        fun cerate():T
    }
    class M{
        companion object :Factory<M>{
            private var mInstance:M=M()
            override fun cerate(): M {
                mInstance=M()
                return mInstance
            }
        }
    }
    /** 4.8 单例类设计
    伴生对象更多的用途是用来创建一个单例类。如果只是简单的写，直接用伴生对象返回一个 val 修饰的外部类对象就可以了，
    但是更多的时候我们希望在类被调用的时候才去初始化他的对象。
    以下代码将线程安全问题交给虚拟机在静态内部类加载时处理，是一种推荐的写法：
     * */
    class Single private constructor() {
        companion object {
            fun get():Single{
                return Holder.instance
            }
        }

        private object Holder {
            val instance = Single()
        }
    }

    /**   4.9 动态代理
    写多继承还是要根据场景来，正好今天跟朋友聊到他们项目重构的问题，我当时就说了一句：果然还是Kotlin好，原生支持动态代理。
    朋友的一个 Android 项目，所有网络请求包括回调和参数全部封装在了一个 BaseActivity 中，然后随着项目越来越大，
    这一些网络请求方法想要抽出来，但又害怕牵连到线上的改动，我就推荐他用个动态代理来做，但是 Java 的动态代理又得要反射，
    又得要额外多写很多的代码方法，又是一个大改动。*/

    interface Animal{
        fun bark()
    }

    class Dog :Animal {
        override fun bark() {
            println("Wang Wang")
        }
    }

    class Cat(animal: Animal) : Animal by animal {
        init{
            Log.i("ObjectActivity","动态代理  猫的叫声用狗去代理掉了")
        }
    }


    /**  4.10 伪多继承
    Kotlin 的动态代理更多的是用在一种需要多继承的场景。
    */

    interface Food{
        fun eat()
    }

    class Delegate : Animal, Food {
        override fun eat() {
            println("mouse")
        }

        override fun bark() {
            println("Miao")
        }
    }

    class Cat2(animal: Animal, food: Food) : Animal by animal, Food by food {
        init{
            Log.i("ObjectActivity","伪多继承  让不同的子类去实现所需的接口，请求统一交给代理类完成")
        }
    }

    /**
     *扩展函数
     * 我们可以为类的伴生对象定义扩展函数，并且可以只通过类名访问扩展函数
    扩展函数永远与函数调用时的类型相同，成员函数则由运行时的具体类型决定。

    https://blog.csdn.net/csdn_gyg/article/details/73187935
    在一个类内部你可以为另一个类声明扩展。在这样的扩展内部，有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。
    扩展声明所在的类的实例称为 分发接收者，扩展方法调用所在的接收者类型的实例称为 扩展接收者 。
    对于分发接收者和扩展接收者的成员名字冲突的情况，扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this 语法。
     */
    class C{
        companion object{}//伴生对象
        fun fun0(){
            Log.i("ObjectActivity","fun0 in C")
        }
    }

    class D{
        fun C.Companion.foo(){//为C类的伴生对象定义扩展
            Log.i("ObjectActivity"," D为C类的伴生对象定义扩展 foo in C.Companion")
        }

        fun printFoo(){
            C.foo()//foo in C.Companion
            //伴生对象的扩展可以直接通过类名访问
        }

        fun fun0(){
            Log.i("ObjectActivity","fun0 in D")
        }

        fun C.foo(){
            fun0()//调用E中的fun0方法
            this@D.fun0()//调用F中的fun0方法
        }
    }

   /**
      4.4.3 data 数据类
     data 修饰的类称之为数据类。它通常用在我们写的一些 POJO 类上。
     当 data 修饰后，会自动将所有成员用operator声明，即为这些成员生成类似 Java 的 getter/setter 方法。
    * 数据类
    * 用data关键字定义一个数据类，编译器会自动从主构造函数中声明的所有属性生成以下成员：
       equals()/hashCode()对
       toString()
       componentN() 按声明顺序对应于所有属性

     主构造函数必须有一个参数
    主构造函数中的参数必须标记为val或var
    数据类不能是抽象，开放，密封，或内部的

   在JVM中，如果主构造函数中的所有参数都有默认值，则编译器会为其自动生成一个无参的构造函数，它会使用默认值。
   我们可以通过copy函数，复制一个对象，改变它的一部分属性，其它属性不变：
    */

   data class Data(val arg1: String="",val arg2:Int=0,val arg3:Boolean=true)

   fun showDataClass(){
        var data=Data()
        var anotherData  = data.copy(arg1 = "Hello Kotlin!",arg2 = 100)
       //改变data对象的arg1属性值为"Hello Kotlin!",arg2属性值为100，arg3属性值保持不变
        showLog("展示数据类型的copy前")
        showLog(data.toString())
        showLog("展示数据类型的copy后")
        showLog(anotherData.toString())

   }


/**
 * 4.4.2 sealed 密封类
sealed 修饰的类称为密封类，用来表示受限的类层次结构。例如当一个值为有限集中的 类型、而不能有任何其他类型时。
在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的，但每个枚举常量只存在一个实例,
而密封类的一个子类可以有可包含状态的多个实例。
Kotlin中的密封类的出现，在于它定义了一种受限的类继承结构，可以保证我们写出更安全的代码。

密封类用来表示受限的类继承结构：当一个值为有限集中的类型、而不能有任何其他类型时。
在某种意义上，他们是枚举类的扩展：枚举类型的值集合也是受限的，但每个枚举常量只存在一个实例，而密封类的一个子类可以有可包含状态的多个实例。
要声明一个密封类，需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类，但是所有子类都必须在与密封类自身相同的文件中声明。
     * 密封类
     * 1.密封类用sealed关键词表示
     * 2.密封类的子类只能定义在密封类的内部或同一个文件中，因为其构造方法为私有的
     * 3.密封类相比于普通的open类，可以不被此文件外被继承，有效保护代码
     * 4.与枚举的区别：密封类适用于子类可数的情况，枚举适用于实例可数的情况
     */
    sealed class MyColor{
        class Yello: MyColor()//黄色
        class Red:MyColor()   // 红色
        class Blue:MyColor()  //蓝色
    }


    fun showsSealedClass(){
        showLog("============== 密封类 ==============")
        val color:MyColor = MyColor.Yello()
        when(color){
            is MyColor.Yello-> showLog("The color is Yello!")
            is MyColor.Red->   showLog("The color is Red")
            is MyColor.Blue->  showLog("The color is Blue")
        }

        val player  = PlayerCmd.Player("",1000L)
        player.showUrl()
    }


    /** 06 枚举类*/
    enum class Direction {
        NORTH,SOUTH,WEST
    }
    enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
    }

    enum class RGB{
        Red,Green,Blue
    }

    fun showEnum(){
        showLog("============== 枚举类 ==============")
        RGB.values()//获取RGB所有的枚举常量
        Direction.values()
        var red:RGB=RGB.valueOf("Red")//获取RGB名称为Red的枚举常量
        showLog( red.name)
        showLog( red.ordinal.toString())

        //kotlin 1.1起
        enumValues<RGB>()//获取RGB所有的枚举常量
        enumValueOf<RGB>("Red")//获取RGB名称为Red的枚举常量
    }




    //外部类
    class Outer{
        var arg1:String=""
        fun foo(){
            LogUtil.i("foo in Outer")
        }
        //嵌套类
        class Nested{
            fun foo(){
                LogUtil.i("foo in Nested")
            }
        }
        //内部类
        inner class Inner{
            fun foo(){
                LogUtil.i("foo in Inner")
                LogUtil.i(this@Outer.arg1) //访问Outer类中的成员属性
                this@Outer.foo()//访问Outer类中过的成员函数
            }
        }
    }
    fun showNestedClass(){
        showLog("============== 嵌套类 ==============")
        val outer  = Outer()
        outer.foo()
        outer.Inner().foo()

        val nested  = Outer.Nested()
        nested.foo()
    }


    class ObjTest{
        //私有函数，返回类型是匿名对象
        private fun foo()=object {
            val str:String="Hello Kotlin 1"
        }

        //公有函数，返回类型是Any
        fun publicFoo()=object {
            val str:String="Hello Kotlin 2"
        }

        fun bar(){
            foo().str //可以访问增加的成员
//            publicFoo().str //无法访问增加的成员
        }
    }


    fun showObject(){
        showLog("============== 对象的私有函数和共有函数 ==============")
        val outer  = ObjTest()
        outer.bar()
        outer.publicFoo()

    }

   /**    对象声明
    这成为对象声明。它总是在object关键字后面跟一个名称。跟变量声明一样，对象声明不是一个表达式，所以不能跟在赋值语句的右边。
     Kotlin，我们可以这样使用一个单例模式：

   对象表达式与对象声明之间的差别：
   对象表达式是在使用它们的地方立即执行的
   对象声明是在第一次被访问到时延迟初始化的
   伴生对象是在相应的类被加载时初始化的，与Java中的静态初始化器语义xiang

    */
    object OkClient{
        private val client = OkHttpClient()
        fun instance() = client
    }

    fun getHtml() {
        showLog("============== 对象声明==============")
//      不能在主线程运行  android.os.NetworkOnMainThreadException
        Thread{
            val client = OkClient.instance()
            val request = Request.Builder()
                    .url("http://www.baidu.com")
                    .build()

            val response = client.newCall(request).execute()
            if(response.body()!=null){
                showLog(response.body()!!.string())
            }else{
                showLog("http://www.baidu.com 请求结果为空")
            }
        }.start()
    }

   /** 解构声明
   我们也可以在for循环中使用解构声明，只要集合中的每个元素提供有componentN()方法

   在解构声明中你如果不需要某个变量，那么可以用下划线取代其名称：var (_,sex,age)=Person()
    * */
   //数据类
   data class DataPerson(val name:String="Kotlin",val sex:String="男",val age:Int=1)

    fun showFoo(){
        var (name,sex,age)=DataPerson() //解构声明赋值
        LogUtil.i("name=$name&sex=$sex&age=$age")
        // 以上申明等价于
        var person=DataPerson()
        var name1=person.component1()//按照声明顺序返回person的第一个属性值，即name属性值
        var sex1=person.component2()//按照声明顺序返回person的第二个属性值，即sex属性值
        var age1=person.component3()//按照声明顺序返回person的第三个属性值，即age属性值
        LogUtil.i("name=$name1&sex=$sex1&age=$age1")

        LogUtil.i(" 对集合使用解构赋值 ")
        var ataPersonList= arrayListOf<DataPerson>()//list集合中元素要为其每个成员属性提供有componentN方法
        ataPersonList.add(person)
        for((name,sex,age) in ataPersonList){
            LogUtil.i("name=$name&sex=$sex&age=$age")
        }

        LogUtil.i(" 对映射使用解构赋值 ")
        var map= mutableMapOf<String,String>()
        map.put("name","Kotlin")
        map.put("sex","男")
        map.put("age","13")
        for ((key,value) in map){
            LogUtil.i("$key=$value")
        }
    }
}
